using Assets.Scripts;
using Assets.Scripts.Inventory;
using Assets.Scripts.Networking;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Entities;
using Assets.Scripts.Objects.Items;
using Assets.Scripts.UI;
using Assets.Scripts.Util;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using UnityEngine;
using UnityEngine.Networking;
using Util.Commands;
using VisualmodBep;
using Weather;

namespace StationeersServerPreference {
    [BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
    [BepInProcess("rocketstation.exe.exe")]
    [BepInProcess("rocketstation_DedicatedServer.exe")]
    public class Plugin : BaseUnityPlugin {
        public static bool IsDedicatedServer { get; private set; }
        private static Harmony _harmony;

        // Plugin startup
        private void Awake() {
            UnityEngine.Object.DontDestroyOnLoad(this);
            InitPlatform();
            InitLog();
            SspConfig.LoadConfig(Config);
            _harmony = HarmonyPatcher.PatcherHelper.Initialize();
            Log.LogInfo("Plugin Awake"); // any log after InitLog()
        }

        // run after CommandLine.ProcessOnLaunch
        private void Start() {
            Log.LogInfo("Plugin Start");
        }

        private void ManualDestroy() {
            Log.LogInfo("Plugin ManualDestroy");
            _harmony.UnpatchSelf();
            Log.OnDestroy();
        }

        // does not run actually
        private void OnDestory() {
            Log.LogInfo("Plugin OnDestory");
        }

        private static int countdown = 100;

        private void Update() {
            if (IsDedicatedServer) {
                return; // no gui or quick key for dedicated server
            }
            if (Input.GetKeyDown(KeyCode.Escape)) {
                Log.LogInfo("ESC keyDown");
                SspGui.CloseWindow();
            } else if (SspConfig.ToggleWindowHotkey.Value.IsDown()) {
                Log.LogInfo("ToggleWindowHotkey keyDown");
                SspGui.ToggleWindow();
            } else if (SspConfig.ToggleWindowHotkey2.Value.IsDown()) {
                Log.LogInfo("ToggleWindowHotkey2 keyDown");
                SspGui.ToggleWindow();
            }
            SspQuickAction.OnUpdate();
            if (--countdown <= 0) {
                countdown = 100;
                UpdatePeriod();
            }
        }

        public void UpdatePeriod() {
            HarmonyPatcher.PatcherQuickAction.FlushLog("");
        }

        // public class SspPlugin : MonoBehaviour {

        public void InitLog() {
            Log.OnLoad("SSP");
            if (!Plugin.IsDedicatedServer) {
                // print to F3 console
                Log.ErrorActions.Add(msg => ConsoleWindow.Print("SSP Error: " + msg, ConsoleColor.Red));
            }
            Log.LogInfo($"Plugin Loading... {DateTime.Now.ToString()}");
            Log.LogInfo($"  Platform {Application.platform} IsDedicatedServer={IsDedicatedServer}");
        }

        /// @ref GameManager.SetMatchMode
        public void InitPlatform() {
            RuntimePlatform platform = Application.platform;
            IsDedicatedServer = Application.isBatchMode || platform == RuntimePlatform.WindowsServer || platform == RuntimePlatform.LinuxServer;
        }

        // } // SspPlugin

    }

    /**
     * Configuration file `BepInEx\config\Stationeers.ServerPreference.cfg`
     * @ref https://docs.bepinex.dev/master/articles/dev_guide/plugin_tutorial/4_configuration.html
     */
    public class SspConfig {
        private static ConfigFile _config;

        #region General
        public static ConfigEntry<KeyboardShortcut> ToggleWindowHotkey;
        public static ConfigEntry<KeyboardShortcut> ToggleWindowHotkey2;
        public static ConfigEntry<string> Locale;
        public static ConfigEntry<bool> OverrideLocalization;
        #endregion

        public static class Difficulty {
            public static ConfigEntry<bool> Enabled;
            public static ConfigEntry<bool> Creative;
            public static ConfigEntry<bool> EatWhileHelmetClosed;
            public static ConfigEntry<bool> DrinkWhileHelmetClosed;
            public static ConfigEntry<float> BreathingRate;
            public static ConfigEntry<float> FoodDecayRate;
            public static ConfigEntry<float> HungerRate;
            public static ConfigEntry<float> HydrationRate;
            public static ConfigEntry<float> HygieneRate;
            public static ConfigEntry<float> JetpackRate;
            public static ConfigEntry<float> LungDamageRate;
            public static ConfigEntry<float> MiningYield;
            public static ConfigEntry<float> MoodRate;
            public static ConfigEntry<float> OfflineMetabolism;
            public static ConfigEntry<float> RobotBatteryRate;
            public static ConfigEntry<float> WeatherLanderDamageRate;
            public static ConfigEntry<string> RespawnCondition;
        }

        public static class GamePlay {
            public static ConfigEntry<bool> HumanGodMode;
            public static ConfigEntry<bool> QuickAction;
            public static ConfigEntry<KeyboardShortcut> QuickActionToolHotkey;
            public static ConfigEntry<KeyboardShortcut> QuickActionSortHotkey;
        }

        public static class Terminal {
            public static ConfigEntry<bool> DisplayAllGas;
        }

        public static void LoadConfig(ConfigFile Config) {
            Log.LogInfo("  Config loading");

            _config = Config;
            ToggleWindowHotkey = Config.Bind("General", "ToggleWindow", new KeyboardShortcut(KeyCode.Quote, KeyCode.LeftAlt));
            ToggleWindowHotkey2 = Config.Bind("General", "ToggleWindow2", new KeyboardShortcut(KeyCode.P, KeyCode.RightControl));
            Locale = Config.Bind("General", "Locale", "zh", new ConfigDescription("Plugin language", new AcceptableValueList<string>(Translation.AcceptLocales)));
            OverrideLocalization = Config.Bind("General", "OverrideLocalization", false, "Custom Translation");

            Difficulty.Enabled = Config.Bind("Difficulty", "Enabled", true, "Enable SSP's difficulty setting");
            Difficulty.Creative = Config.Bind("Difficulty", "Creative", true);
            Difficulty.EatWhileHelmetClosed = Config.Bind("Difficulty", "EatWhileHelmetClosed", false);
            Difficulty.DrinkWhileHelmetClosed = Config.Bind("Difficulty", "DrinkWhileHelmetClosed", false);
            Difficulty.BreathingRate = Config.Bind("Difficulty", "BreathingRate", 2f);
            Difficulty.FoodDecayRate = Config.Bind("Difficulty", "FoodDecayRate", 1f);
            Difficulty.HungerRate = Config.Bind("Difficulty", "HungerRate", 1f);
            Difficulty.HydrationRate = Config.Bind("Difficulty", "HydrationRate", 1f);
            Difficulty.HygieneRate = Config.Bind("Difficulty", "HygieneRate", 1f);
            Difficulty.JetpackRate = Config.Bind("Difficulty", "JetpackRate", 1f);
            Difficulty.LungDamageRate = Config.Bind("Difficulty", "LungDamageRate", 0.25f);
            Difficulty.MiningYield = Config.Bind("Difficulty", "MiningYield", 1f);
            Difficulty.MoodRate = Config.Bind("Difficulty", "MoodRate", 1f);
            Difficulty.OfflineMetabolism = Config.Bind("Difficulty", "OfflineMetabolism", 0.1f);
            Difficulty.RobotBatteryRate = Config.Bind("Difficulty", "RobotBatteryRate", 1f);
            Difficulty.WeatherLanderDamageRate = Config.Bind("Difficulty", "WeatherLanderDamageRate", 1f);
            Difficulty.RespawnCondition = Config.Bind("Difficulty", "RespawnCondition", "Normal");

            GamePlay.HumanGodMode = Config.Bind("Gameplay", "HumanGodMode", false);
            GamePlay.QuickAction = Config.Bind("Gameplay", "QuickAction", false);
            GamePlay.QuickActionToolHotkey = Config.Bind("Gameplay", "QuickActionToolHotkey", new KeyboardShortcut(KeyCode.F6));
            GamePlay.QuickActionSortHotkey = Config.Bind("Gameplay", "QuickActionSortHotkey", new KeyboardShortcut(KeyCode.F7));

            Terminal.DisplayAllGas = Config.Bind("Terminal", "DisplayAllGas", true);

            Log.LogInfo("  Config loaded");

            // set core HideManagerGameObject to true
            MethodInfo CoreConfigGetter = AccessTools.PropertyGetter(typeof(ConfigFile), "CoreConfig");
            if (CoreConfigGetter == null) {
                Log.LogError("BepInEx.Configuration.ConfigFile.CoreConfig not found");
            } else if (!CoreConfigGetter.IsStatic) {
                Log.LogError("BepInEx.Configuration.ConfigFile.CoreConfig is not a static getter.");
            } else {
                ConfigFile CoreConfig = CoreConfigGetter.Invoke(null, []) as ConfigFile;
                if (CoreConfig == null) {
                    Log.LogError("BepInEx.Configuration.ConfigFile.CoreConfig failed to get.");
                } else {
                    ConfigEntry<bool> entry = CoreConfig["Chainloader", "HideManagerGameObject"] as ConfigEntry<bool>;
                    if (entry != null) {
                        entry.Value = true;
                    }
                    Log.LogInfo("  Set BepInEx Chainloader.HideManagerGameObject to true.");
                }
            }
        }
    }

    public class SspWeather {
        /**
         * @ref Util.Commands.StormCommand.Execute(string[]) : string
         * @ref WeatherManager.SendWeatherEffect(bool enabled) : void
         * @ref WeatherManager.StartScheduleServer() : void
         * @ref WeatherManager.EnableWeatherEffectsServer
         */
        // public static void SendWeatherEffect(bool enabled) {
        //     if (NetworkManager.IsClient) {
        //         new WeatherToggleMessage {
        //             FogColor = WeatherManager.FogColor,
        //             SolarGlobalGenerationEfficiency = enabled ? 0.1f : 1f,
        //             StormDirection = WeatherManager.StormDirectionForwardEnum,
        //             Toggle = enabled
        //         }.SendToServer();
        //     } else {
        //         ServerSetWeather(enabled); // hosted or server
        //     }
        // }

        /// in game and loaded
        public static bool IsReady {
            get => InventoryManager.ParentBrain != null;
        }

        public static void ServerSetWeather(bool enable) {
            if (!IsReady) {
                Log.LogInfo("ServerSetWeather not ready.");
                return;
            }
            if (enable) {
                WeatherManager.ImmediatelyActivateWeatherEvent();
            } else {
                WeatherManager.StopCurrentWeatherEvent();
            }
        }

        public static bool IsWeatherEventRunning {
            get => WeatherManager.IsWeatherEventRunning;
        }

        public static bool HasWeather {
            get => IsReady && WeatherManager.WorldHasWeather;
        }

        // @ref WeatherManager.DrawDebug()
        public static float WeatherTimeRemaining {
            get => WeatherManager.WeatherStartTime + WeatherManager.WeatherEventLength - GameManager.GameTime;
        }
    }

    public class Util {
        // @ref Util.Commands.ThingCommand.GetHumanRefId(out string)
        public static Human GetHuman(bool noWarn = false) {
            if (GameManager.IsBatchMode) {
                if (Human.AllHumans.Count >= 0) {
                    Log.LogInfo($"GetHuman batch to {Human.AllHumans[0].DisplayName}");
                    return Human.AllHumans[0];
                }
                if (!noWarn) {
                    Log.LogError("GetHuman no humans");
                }
                return null;
            } else {
                if (InventoryManager.ParentHuman) {
                    return InventoryManager.ParentHuman;
                }
                if (!noWarn) {
                    Log.LogError("GetHuman inventory parent not found");
                }
                return null;
            }
        }

        // @ref Util.Commands.ThingCommand.GetHumanRefId(out string)
        public static long GetHumanRefId() {
            Human human = GetHuman();
            return human == null ? -1L : human.ReferenceId;
        }

        public static void RefreshHumanWrap(Human human = null) {
            if (NetworkManager.IsClient) {
                ulong clientId = GetHuman(true).OrganBrain.ClientId;
                string command = $"serverrun ssp refresh {clientId}";
                Log.LogInfo(command);
                CommandLine.Process(command);
            } else {
                SspQuickAction.RefreshHuman(human);
            }
        }
    }

    namespace HarmonyPatcher {
        public class PatcherHelper {
            public static int Patch(ref Harmony harmony, params Type[] types) {
                foreach (Type t in types) {
                    harmony.PatchAll(t);
                }
                return types.Length;
            }

            public static Harmony Initialize() {
                Harmony harmony = new Harmony(PluginInfo.PLUGIN_GUID);
                int patchers = 0;
                patchers += Patch(ref harmony,
                    typeof(Patcher),
                    typeof(PatcherDifficulty),
                    typeof(PatcherLocalization),
                    typeof(PatcherQuickAction),
                    typeof(PatcherSendToServer),
                    typeof(PatherServerRunCommandMessage)
                );
                int methods = harmony.GetPatchedMethods().ToList().Count;
                Log.LogInfo($"  Patched {methods} methods with {patchers} patchers");
                return harmony;
            }
        }

        [HarmonyPatch]
        public class PatherServerRunCommandMessage {
            public static Type TypeServerRunCommandMessage = null;

            // @ref UnityEngine.Networking.MessageFactory.MessageTypeToIndex
            // @ref NetworkBase.DeserializeReceivedData
            static MethodBase TargetMethod() {
                FieldInfo IndexToMessageType = AccessTools.Field(typeof(MessageFactory), "IndexToMessageType");
                if (IndexToMessageType == null) {
                    Log.LogError("IndexToMessageType not found");
                    return null;
                }
                Type[] messageTypes = IndexToMessageType.GetValue(null) as Type[];
                foreach (Type msg in messageTypes) {
                    if (msg.FullName == "Util.Commands.ServerRunCommandMessage") {
                        TypeServerRunCommandMessage = msg;
                        MethodInfo method = msg.GetMethod("Deserialize");
                        if (method == null) {
                            Log.LogError($"MakeGenericMethod error {msg}");
                        } else {
                            return method;
                        }
                    }
                }
                return null;
            }

            static bool Prefix(ref RocketBinaryReader reader) {
                Log.LogInfo($"PREF ServerRunCommandMessage.Deserialize");
                ulong ClientId = reader.ReadUInt64();
                string Secret = reader.ReadString();
                string Command = reader.ReadString();
                if (!Command.StartsWith("ssp ")) {
                    MemoryStream stream = new MemoryStream();
                    RocketBinaryWriter writer = new RocketBinaryWriter(stream);
                    writer.WriteUInt64(ClientId);
                    writer.WriteString(Secret);
                    writer.WriteString(Command);
                    reader = new RocketBinaryReader(stream);
                    return true; // continue
                }
                Log.LogInfo($"  {ClientId}: {Command}");
                string[] args = Command.Substring(4).Split(' ').Where(s => !String.IsNullOrEmpty(s)).ToArray();
                if (args.Length >= 1) {
                    switch (args[0]) {
                    case "refresh":
                        foreach (Human human in Human.AllHumans) {
                            if (human.OrganBrain.ClientId == ClientId) {
                                SspQuickAction.RefreshHuman(human);
                            }
                        }
                        break;
                    case "terminal":
                        SspTerminalCommand.ParseArgs(args.Skip(1).ToArray());
                        break;
                    }
                }
                return false; // override
            }
        }
    }

    public class Patcher {
        [HarmonyPatch(typeof(DLC.SharedDLCManager), "HostFinishedLoad")]
        [HarmonyPostfix]
        static void Patch_DLC_SharedDLCManager_HostFinishedLoad() {
            Log.LogInfo("POST DLC.SharedDLCManager.HostFinishedLoad");
        }

        [HarmonyPatch(typeof(WorldManager), "StartWorld")]
        [HarmonyPostfix]
        static void Patch_WorldManager_StartWorld() {
            Log.LogInfo("POST WorldManager.StartWorld");
        }

        [HarmonyPatch(typeof(CommandLine), "ProcessOnLaunch")]
        [HarmonyPostfix]
        static void Patch_CommandLine_ProcessOnLaunch() {
            Log.LogInfo("POST CommandLine.ProcessOnLaunch");
            string helpStr = "ssp <diff|dump|help|localize|refresh|ui> [args]";
            CommandLine.AddCommand("ssp", new BasicCommand(delegate (string[] args) {
                if (args.Length == 0) {
                    return helpStr;
                }
                switch (args[0]) {
                case "diff":
                    if (args.Length >= 3) {
                        for (int i = 2; i < args.Length; i += 2) {
                            SspDifficulty.Setter.SetCommand(args[i - 1], args[i]);
                        }
                    } else if (args.Length == 2 && args[1] == "help") {
                        ConsoleWindow.Print("Print difficulty: ssp diff", ConsoleColor.White);
                        ConsoleWindow.Print("Set   difficulty: ssp diff <property> <value> ...", ConsoleColor.White);
                    } else if (DifficultySetting.Current == null) {
                        ConsoleWindow.Print("Current difficulty not found", ConsoleColor.Red);
                    } else {
                        SspDifficulty.Print(DifficultySetting.Current, (string str) => ConsoleWindow.Print(str));
                    }
                    break;
                case "dump":
                    if (args.Length == 1) {
                        ConsoleWindow.Print("ssp dump <diff|things>", ConsoleColor.Yellow);
                        break;
                    }
                    Dumper.DumpCommand.Process(args[1], args.Skip(2));
                    break;
                case "help":
                    ConsoleWindow.Print(helpStr, ConsoleColor.Yellow);
                    ConsoleWindow.Print("ssp diff                - Print current difficulty");
                    ConsoleWindow.Print("ssp diff type value ... - Set current difficulty of 'type' to 'value' (float or string)");
                    ConsoleWindow.Print("ssp dump items          - Dump all items info to clipboard");
                    ConsoleWindow.Print("ssp ui                  - Toggle UI");
                    break;
                case "localize":
                    SspLocalization.Run();
                    break;
                case "refresh": // @TODO refresh remotely
                    ulong clientId;
                    if (args.Length >= 2 && ulong.TryParse(args[1], out clientId)) {
                        foreach (Human human in Human.AllHumans) {
                            if (human.OrganBrain.ClientId == clientId) {
                                SspQuickAction.RefreshHuman(human);
                            }
                        }
                    } else {
                        foreach (Human human in Human.AllHumans) {
                            if (Client.Find(human.OrganBrain.ClientId) != null) {
                                ConsoleWindow.Print($"{human.OrganBrain.ClientId} {human.DisplayName}", ConsoleColor.Green);
                            } else {
                                ConsoleWindow.Print($"{human.OrganBrain.ClientId} {human.DisplayName}", ConsoleColor.Yellow);
                            }
                        }
                    }
                    break;
                case "ui":
                    SspGui.ToggleWindow();
                    break;
                default:
                    ConsoleWindow.Print($"Unknown opreate \"{args[0]}\"", ConsoleColor.Red);
                    break;
                }
                return null;
            },
            helpStr,
            new string[] { "help", "diff <?property> <?value> ...", "dump <type>", "localize", "refresh", "ui" },
            false));
        }

        [HarmonyPatch(typeof(InventoryManager), nameof(InventoryManager.Initialize), new Type[] { })]
        [HarmonyPostfix]
        static void Patch_InventoryManager_Initialize() {
            Log.LogInfo("POST InventoryManager.Initialize()");
            if (!Plugin.IsDedicatedServer) {
                SspGui.Instance.Initialize();
            }
        }

        [HarmonyPatch(typeof(InventoryManager), nameof(InventoryManager.Initialize), typeof(Entity))]
        [HarmonyPostfix]
        static void Patch_InventoryManager_Initialize(Entity parent) {
            Log.LogInfo("POST InventoryManager.Initialize(Entity)");
            if (!Plugin.IsDedicatedServer) {
                SspGui.Instance.Initialize();
            }
        }

        [HarmonyPatch(typeof(Localization), nameof(Localization.GetKeyName))]
        [HarmonyPrefix]
        static void Patch_Localization_GetKeyName(KeyCode keyCode, bool stripSpecial) {
            // Dictionary<int, string> keyName = AccessTools.StaticFieldRefAccess<Dictionary<int, string>>(typeof(Localization), "KeyName");
            // string text;
            // keyName.TryGetValue(Animator.StringToHash(key), out text);
            // Log.LogInfo($"PREF Localization.GetKeyName {key} '{text}'");
        }

        // Server Only?
        [HarmonyPatch(typeof(FragmentHandler), "Send")]
        [HarmonyPrefix]
        static void Patch_WeatherManager_Send(bool logging) {
            if (!NetworkManager.IsServer) {
                Log.LogInfo($"PREF FragmentHandler.Send {logging}");
            }
        }

        // Client Only?
        [HarmonyPatch(typeof(FragmentHandler), "Receive")]
        [HarmonyPrefix]
        static void Patch_FragmentHandler_Receive(ref byte[] bytes, int size, bool isServer) {
            if (!NetworkManager.IsClient) {
                Log.LogInfo($"PREF FragmentHandler.Receive {size} {isServer}");
            }
        }

    }

    // @ref https://github.com/pardeike/Harmony/issues/121
    [HarmonyPatch]
    public class PatcherSendToServer {
        public static readonly List<Type> messageTypes = new List<Type>();

        private static readonly string[] excludeMessage = { "Assets.Scripts.Networking.ResearchUnlockedMessageClient" };
        private static string LastHandshakeState = "";

        // @ref UnityEngine.Networking.MessageFactory.MessageTypeToIndex
        static IEnumerable<MethodBase> TargetMethods() {
            // refer to C# reflection documentation:
            // Log.LogInfo("Patch_NetworkClient_SendToServer");
            FieldInfo IndexToMessageType = AccessTools.Field(typeof(MessageFactory), "IndexToMessageType");
            if (IndexToMessageType == null) {
                Log.LogError("IndexToMessageType not found");
                return null;
            }
            messageTypes.AddRange(IndexToMessageType.GetValue(null) as Type[]);
            Log.LogInfo($"  IndexToMessageType {messageTypes.Count}"); // should be 149 in vanilla game

            List<MethodInfo> methods = new List<MethodInfo>();
            foreach (Type msg in messageTypes) {
                // Log.LogInfo($"  {msg.FullName} base {msg.BaseType.Name}");
                if (!excludeMessage.Contains(msg.FullName)) {
                    MethodInfo method = typeof(NetworkClient).GetMethod("SendToServer").MakeGenericMethod(msg);
                    if (method == null) {
                        Log.LogError($"MakeGenericMethod error {msg}");
                    } else {
                        methods.Add(method);
                    }
                }
            }
            return methods;
        }

        static void Prefix(object[] __args) {
            var message = __args[0] as IMessageSerialisable;
            NetworkChannel channel = (NetworkChannel)__args[1];

            if (message is NetworkMessages.Handshake) {
                NetworkMessages.Handshake handshake = message as NetworkMessages.Handshake;
                if (handshake.LoadingState != LastHandshakeState) {
                    LastHandshakeState = handshake.LoadingState;
                    Log.LogInfo($"PREF NetworkClient.SendToServer HandShake {handshake.LoadingState}");
                }
                return;
            } else if (message is OwnerMessage || message is NetworkMessages.JetpackStateMessage) {
                // @ref Assets.Scripts.Objects.Entities.Human.BuildOwnerUpdate(RocketBinaryWriter) : void
                // @brief Update human position, pose, animation...
                // OwnerMessage owener = message as OwnerMessage;
                // Log.LogInfo($"PREF NetworkClient.SendToServer OwnerMessage");
                return;
            }

            string s = message.GetType().Name;
            if (channel != NetworkChannel.GeneralTraffic) {
                s += ' ' + channel;
            }
            Log.LogInfo($"PREF NetworkClient.SendToServer {s}");
        }
    }

    namespace Dumper {
        public interface IDumper : Helper.IInitialize {
            string GetString();
        }

        public class DumperBase<Tp> : Helper.Singleton<Tp> where Tp : IDumper, new() {
            public override void Initialize() {
                return;
            }

            public virtual string GetString() {
                throw new NotImplementedException();
            }

            public static void Clipboard() {
                GUIUtility.systemCopyBuffer = Instance.GetString();
                ConsoleWindow.Print("dumped to clipboard", ConsoleColor.Yellow);
            }
        }

        public class DumpCommand {
            public static void Process(string category, params object[] args) {
                switch (category) {
                case "diff":
                    Dumper.DifficultyDumper.Clipboard();
                    break;
                case "things":
                    Dumper.DynamicThingDumper.Clipboard();
                    break;
                case "prefab":
                    Dumper.PrefabDumper.Clipboard();
                    break;
                case "messages":
                    GUIUtility.systemCopyBuffer = String.Join("\n", PatcherSendToServer.messageTypes);
                    ConsoleWindow.Print("dumped to clipboard", ConsoleColor.Yellow);
                    break;
                default:
                    ConsoleWindow.Print("Unknown option", ConsoleColor.Red);
                    break;
                }
            }
        }
    }

    namespace Helper {
        public interface IInitialize {
            void Initialize();
        }

        public class Singleton<T> where T : IInitialize, new() {
            private static T _instance;
            public static T Instance {
                get {
                    if (_instance == null) {
                        _instance = new T();
                        _instance.Initialize();
                    }
                    return _instance;
                }
            }

            public virtual void Initialize() {
                throw new NotImplementedException();
            }
        }
    }
}